package org.dromara.frame.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.vcp.VcpUtil;
import org.dromara.common.vcp.domain.SzSecret;
import org.dromara.device.api.RemoteDeviceService;
import org.dromara.device.api.domain.vo.ApiDeviceVo;
import org.dromara.frame.api.RemoteFrameService;
import org.dromara.frame.api.domain.FrameImage;
import org.dromara.resource.api.RemoteFileService;
import org.dromara.resource.api.domain.RemoteFile;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

@Log4j2
@DubboService(timeout = 20000)
public class RemoteFrameServiceImpl implements RemoteFrameService {

    @DubboReference
    private RemoteDeviceService remoteDeviceService;
    @DubboReference(timeout = 20000)
    private RemoteFileService remoteFileService;
    // 根据实际情况配置线程池
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);


    @Override
    public List<FrameImage> extraction(List<Long> deviceIds) throws ExecutionException, InterruptedException, TimeoutException {
        List<ApiDeviceVo> apiDeviceVos = remoteDeviceService.queryByDeviceIds(deviceIds);
        List<CompletableFuture<FrameImage>> futures = new ArrayList<>();

        for (ApiDeviceVo deviceVo : apiDeviceVos) {
            CompletableFuture<FrameImage> frameImageCompletableFuture = CompletableFuture.supplyAsync(() ->
                            extractFrameImageFromVideo(deviceVo), executorService)
                    .thenApplyAsync((frameImage) -> {
                        // 上传图片到minio服务器中
                        if (ObjUtil.isNotNull(frameImage)) {
                            return uploadImage(frameImage, deviceVo);
                        }
                        return null;
                    }, executorService)
                    .whenCompleteAsync((result, error)-> {
                        if (null != error && ObjUtil.isNotNull(result)) {
                            // 同一个设备 缓存4分钟
                            String imageRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + "extraction:" + deviceVo.getDeviceSn() + ":imageUrl";
                            RedisUtils.setCacheObject(imageRedisKey, result, Duration.ofMinutes(4));
                        }
                    }, executorService);

            futures.add(frameImageCompletableFuture);
        }

        // 等待所有的 CompletableFuture 完成
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

        // 使用 thenAccept 来处理每个图片，这里简单打印出来
        List<FrameImage> images = new ArrayList<>();
        // 阻塞，直到所有图片处理完毕 20秒是极限阻塞时间
        allFutures.thenAccept(v -> futures.forEach(future -> future.thenAccept(frameImage -> {
            if (ObjUtil.isNotNull(frameImage)) {
                images.add(frameImage);
            }
        }))).get(20, TimeUnit.SECONDS);

        return images;
    }


    private FrameImage extractFrameImageFromVideo(ApiDeviceVo apiDeviceVo) {
        // 获取缓存中的图片
        // String imageRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + "extraction:" + apiDeviceVo.getDeviceSn() + ":imageUrl";
        // FrameImage frameImage = getCacheByKey(imageRedisKey);
        // if (ObjUtil.isNotNull(frameImage)) {
        //     return frameImage;
        // }

        String videoFilePath = getLiveUrl(apiDeviceVo);
        if (StrUtil.isBlank(videoFilePath)) {
            log.error("获取视频流失败! 设备id：{}", apiDeviceVo.getDeviceId());
            return null;
        }

        String filePath = "/home/work/files/";
        File directory = new File(filePath);
        if (!directory.exists()) {
            // 使用mkdirs()可创建多级目录
            boolean success = directory.mkdirs();
            if (success) {
                log.info("目录:{}已创建", filePath);
            } else {
                log.info("创建目录:{}失败", filePath);
            }
        }
        // 输出的图片地址
        String outputImagePath = filePath + apiDeviceVo.getDeviceSn() + ".jpg";

        // 读取视频流
        VideoCapture videoCapture = new VideoCapture(videoFilePath);

        if (!videoCapture.isOpened()) {
            log.error("打开视频流失败");
            throw new ServiceException("打开视频流失败");
        }

        // 设置分辨率和帧率
        videoCapture.set(Videoio.CAP_PROP_FRAME_WIDTH, 640);
        videoCapture.set(Videoio.CAP_PROP_FRAME_HEIGHT, 480);
        videoCapture.set(Videoio.CAP_PROP_FPS, 48);

        Mat frame = new Mat();
        // 跳过的帧数
        for (int i = 0; i < 1; i++) {
            videoCapture.read(frame);
        }
        // 获取当前抽帧
        videoCapture.read(frame);
        // 保存抽帧到图片
        Imgcodecs.imwrite(outputImagePath, frame);

        // 释放VideoCapture
        videoCapture.release();

        // 释放Mat对象
        frame.release();

        FrameImage frameImage = new FrameImage();
        frameImage.setDeviceId(apiDeviceVo.getDeviceId());
        frameImage.setImgUrl(outputImagePath);
        return frameImage;
    }

    private FrameImage uploadImage(FrameImage frameImage, ApiDeviceVo deviceVo) {

        byte[] bytes = FileUtils.readBytes(frameImage.getImgUrl());
        RemoteFile remoteFile = remoteFileService.upload(deviceVo.getTenantId(), bytes);

        try {
            Files.deleteIfExists(Paths.get(frameImage.getImgUrl()));
        } catch (IOException e) {
            log.error("删除图片失败！ 图片： {}", frameImage.getImgUrl());
        }
        frameImage.setImgUrl(remoteFile.getUrl());
        return frameImage;

    }

    /**
     * 从缓存中获取设备的缓存图片地址
     * @param imageRedisKey 图片缓存key
     * @return              图片地址
     */
    private FrameImage getCacheByKey(String imageRedisKey) {
        return RedisUtils.getCacheObject(imageRedisKey);
    }

    private String getLiveUrl(ApiDeviceVo apiDeviceVo) {
        String redisKey = GlobalConstants.GLOBAL_REDIS_KEY + "frame:" + apiDeviceVo.getDeviceSn() + ":" + apiDeviceVo.getAppId();

        String videoFilePath = RedisUtils.getCacheObject(redisKey);

        if (StrUtil.isBlank(videoFilePath)) {
            // 根据设备获取播放流
            SzSecret szSecret = BeanUtil.toBean(apiDeviceVo, SzSecret.class);
            videoFilePath = VcpUtil.getLiveUrl(szSecret, null);
            if (StrUtil.isBlank(videoFilePath)) {
                log.info("视频流获取失败! 原因： {}", videoFilePath );
                return null;
            }
            // 4分钟过期时间
            RedisUtils.setCacheObject(redisKey, videoFilePath, Duration.ofMinutes(4));
        }
        return videoFilePath;
    }
}
